package org.eden.karb.device.audio.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eden.karb.common.util.AudioUtils;
import org.eden.karb.configuration.Configuration;
import org.eden.karb.configuration.ConfigurationService;
import org.eden.karb.device.Device;
import org.eden.karb.device.DeviceFactory;
import org.eden.karb.device.audio.AudioConfiguration;
import org.eden.karb.device.audio.AudioDeviceService;

import javax.annotation.Resource;
import javax.sound.sampled.*;
import java.text.MessageFormat;

/**
 * {@inheritDoc}
 */
public class AudioDeviceServiceImpl implements AudioDeviceService {

    private Log log = LogFactory.getLog(AudioDeviceServiceImpl.class);

    private int mixerDeviceIndex;

    @Resource
    private ConfigurationService configurationService;

    @SuppressWarnings("unchecked")
    public <T extends Device> T getDevice(Class<T> deviceType) {

        T audioDevice = null;

        try {
            Mixer mixer = getDeviceController();

            Line.Info targetDLInfo = new Line.Info(TargetDataLine.class);
            TargetDataLine targetRecordLine = (TargetDataLine) mixer.getLine(targetDLInfo);
            targetRecordLine.open(AudioUtils.getDefaultAudioFormat());
            targetRecordLine.start();

            AudioConfiguration configuration = new AudioConfiguration()
                    .setTargetRecordLine(targetRecordLine)
                    .setBufferSize(configurationService.get(Configuration.BUFFER_SIZE, Integer.class))
                    .setInputRefreshRate(configurationService.get(Configuration.SYSTEM_INPUT_REFRESH_RATE,
                            Integer.class))
                    .setTimeBetweenEvents(configurationService.get(Configuration.PUBLISH_TIME_BETWEEN_EVENTS,
                            Integer.class));

            audioDevice = (T) DeviceFactory.getDevice(deviceType, configuration);
        } catch (LineUnavailableException e) {
            log.error("Line is unavailable.", e);
        }
        return audioDevice;
    }

    private Mixer getDeviceController() {

        Mixer.Info info = getMixerInfoByIndex(mixerDeviceIndex);

        if (info == null) {
            info = getMixerInfoByIndex(configurationService.get(Configuration.DEFAULT_MIXER_INDEX, Integer.class));
        }

        return AudioSystem.getMixer(info);
    }

    private Mixer.Info getMixerInfoByIndex(int mixerIndex) {

        Mixer.Info mixerInfo = null;

        Mixer.Info[] mixersInfo = AudioSystem.getMixerInfo();

        if (mixersInfo != null) {
            if (mixersInfo.length > 0) {
                if (mixersInfo.length >= mixerIndex) {
                    mixerInfo = AudioSystem.getMixerInfo()[mixerIndex];
                } else {
                    log.error(MessageFormat.format("The system only has {0} audio mixers. Mixer {1} is not available.",
                            mixersInfo.length, mixerIndex));
                }
            } else {
                log.error("No audio mixer is available.");
            }
        } else {
            log.error("Failed to load the systems' audio mixers.");
        }

        return mixerInfo;
    }

    public void setMixerDeviceIndex(int mixerDeviceIndex) {
        this.mixerDeviceIndex = mixerDeviceIndex;
    }
}
